﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using Microsoft.Practices.ServiceLocation;

namespace SimpleContainer.Extensions
{
    public static class SimpleContainerExtensions
    {

        #region more overloads on core method

        public static void Register(this SimpleContainer container, Type type, Func<SimpleContainer, Type, string, object> creator)
        {
            container.Register(type, null, creator);
        }


        public static void Register<T>(this SimpleContainer container, string key, Func<SimpleContainer, Type, string, object> creator)
        {
            container.Register(typeof(T), key, creator);
        }

        public static void Register<T>(this SimpleContainer container, Func<SimpleContainer, Type, string, object> creator)
        {
            container.Register<T>(null, creator);
        }

        #endregion



        #region instance registration

        public static void RegisterInstance(this SimpleContainer container, Type type, string key, object instance)
        {
            container.Register(type, key, (c, t, k) => instance);
        }

        public static void RegisterInstance(this SimpleContainer container, Type type, object instance)
        {
            container.Register(type, null, (c, t, k) => instance);
        }

        public static void RegisterInstance<T>(this SimpleContainer container, string key, T instance)
        {
            container.Register(typeof(T), key, (c, t, k) => instance);
        }

        public static void RegisterInstance<T>(this SimpleContainer container, T instance)
        {
            container.Register(typeof(T), null, (c, t, k) => instance);
        }

        #endregion



        #region singleton registration

        public static void RegisterSingleton(this SimpleContainer container, Type type, string key, object instance)
        {
            var asIDisposable = instance as IDisposable;
            if (asIDisposable != null)
            {
                container.Disposing += (sender, eventArgs) =>
                {
                    if (asIDisposable != null)
                    {
                        try
                        {
                            asIDisposable.Dispose();
                        }
                        finally
                        {
                            asIDisposable = null;
                        }
                    }
                };
            }
            container.Register(type, key, (c, t, k) => instance);
        }

        public static void RegisterSingleton(this SimpleContainer container, Type type, object instance)
        {
            container.Register(type, null, (c, t, k) => instance);
        }

        public static void RegisterSingleton<T>(this SimpleContainer container, string key, T instance)
        {
            container.Register(typeof(T), key, (c, t, k) => instance);
        }

        public static void RegisterSingleton<T>(this SimpleContainer container, T instance)
        {
            container.Register(typeof(T), null, (c, t, k) => instance);
        }

        #endregion



        #region implementing type registration

        public static Func<SimpleContainer, Type, string, object> GetCreator(Type type)
        {
            if (type == null)
            {
                throw new ArgumentNullException("type");
            }
            var ctors = type.GetConstructors();
            if (ctors == null || ctors.Length == 0)
            {
                throw new ActivationException(string.Format("Appropriate constructor not found in type '{0}'.", type.FullName));
            }

            var ctor = ctors[0];

            var containerParameter = Expression.Parameter(typeof(SimpleContainer), "container");
            var typeParameter = Expression.Parameter(typeof(Type), "type");
            var keyParameter = Expression.Parameter(typeof(string), "key");

            var creator = Expression.Lambda(typeof(Func<SimpleContainer, Type, string, object>),
                Expression.New(ctor, ctor.GetParameters().Select(p => (Expression)Expression.Call(containerParameter, "GetInstance", new[] { p.ParameterType }))),
                containerParameter, typeParameter, keyParameter);
            return (Func<SimpleContainer, Type, string, object>)creator.Compile();
        }

        public static Func<SimpleContainer, Type, string, object> GetCreator<T>()
        {
            return GetCreator(typeof(T));
        }


        public static void Register(this SimpleContainer container, Type serviceType, string key, Type implementationType)
        {
            container.Register(serviceType, key, GetCreator(implementationType));
        }

        public static void Register(this SimpleContainer container, Type serviceType, Type implementationType)
        {
            container.Register(serviceType, null, implementationType);
        }

        public static void Register<TService, TImplementation>(this SimpleContainer container, string key)
        {
            container.Register(typeof(TService), key, typeof(TImplementation));
        }

        public static void Register<TService, TImplementation>(this SimpleContainer container)
        {
            container.Register(typeof(TService), null, typeof(TImplementation));
        }

        #endregion




        #region first implementation registration

        public static void RegisterFirstImplementation(this SimpleContainer container, string key, Type serviceType)
        {
            if (serviceType == null)
            {
                throw new ArgumentNullException("serviceType");
            }
            var impl = AppDomain.CurrentDomain.GetAssemblies()
                .Where(a => a.FullName != null && !a.FullName.Contains("mscorlib") && !a.FullName.StartsWith("System") && !a.FullName.StartsWith("Microsoft"))
                .SelectMany(a => a.GetTypes().Where(t => !t.IsAbstract && serviceType.IsAssignableFrom(t)))
                .FirstOrDefault();
            if (impl == null)
            {
                throw new InvalidOperationException(string.Format("Implementation for '{0}' not found.", serviceType.FullName));
            }
            container.Register(serviceType, key, impl);
        }

        public static void RegisterFirstImplementation(this SimpleContainer container, Type serviceType)
        {
            container.RegisterFirstImplementation(null, serviceType);
        }

        public static void RegisterFirstImplementation<T>(this SimpleContainer container, string key)
        {
            container.RegisterFirstImplementation(key, typeof(T));
        }

        public static void RegisterFirstImplementation<T>(this SimpleContainer container)
        {
            container.RegisterFirstImplementation(null, typeof(T));
        }

        #endregion



        #region resolving redirection

        public static void RedirectResolving(this SimpleContainer container, Type serviceType, string key, Type targetType)
        {
            container.Register(serviceType, key, (c, t, k) => c.GetInstance(targetType, k));
        }

        public static void RedirectResolving(this SimpleContainer container, Type serviceType, Type targetType)
        {
            container.Register(serviceType, null, (c, t, k) => c.GetInstance(targetType, k));
        }

        public static void RedirectResolving<T, TTarget>(this SimpleContainer container, string key)
        {
            container.Register<T>(key, (c, t, k) => c.GetInstance<TTarget>(k));
        }

        public static void RedirectResolving<T, TTarget>(this SimpleContainer container)
        {
            container.Register<T>(null, (c, t, k) => c.GetInstance<TTarget>(k));
        }

        #endregion




        public static void RegisterFirstImplementationsOfInterfacesDerivedFrom<TService>(this SimpleContainer container)
        {
            foreach (var iface in AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes().Where(t => t.IsInterface && typeof(TService).IsAssignableFrom(t))))
            {
                var type = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes().Where(t => t.IsClass && !t.IsAbstract && iface.IsAssignableFrom(t))).FirstOrDefault();
                if (type == null)
                {
                    throw new InvalidOperationException(string.Format("Implementation for '{0}' interface not found.", iface.FullName));
                }
                container.Register(iface, type);
            }
        }



        public static SimpleContainer CreatePermanentChildContainer(this SimpleContainer container)
        {
            return new SimpleContainer { ParentContainer = container };
        }

        public static SimpleContainer CreateChildContainer(this SimpleContainer container)
        {
            var child = container.CreatePermanentChildContainer();
            container.Disposing += (sender, eventArgs) => child.Dispose();
            return child;
        }


        #region registration overriding

        public static void RegisterWithOverride(this SimpleContainer container, Type serviceType, string key, Func<SimpleContainer, Type, string, object> creator, Action<SimpleContainer> overrideContainer)
        {
            if (serviceType == null)
            {
                throw new ArgumentNullException("serviceType");
            }
            if (creator == null)
            {
                throw new ArgumentNullException("creator");
            }
            if (overrideContainer == null)
            {
                throw new ArgumentNullException("overrideContainer");
            }
            container.Register(serviceType, key, (c, t, k) =>
             {
                 using (var cc = container.CreatePermanentChildContainer())
                 {
                     overrideContainer(cc);
                     return creator(cc, t, k);
                 }
             });
        }

        public static void RegisterWithOverride(this SimpleContainer container, Type serviceType, Func<SimpleContainer, Type, string, object> creator, Action<SimpleContainer> overrideContainer)
        {
            container.RegisterWithOverride(serviceType, null, creator, overrideContainer);
        }

        public static void RegisterWithOverride<T>(this SimpleContainer container, string key, Func<SimpleContainer, Type, string, object> creator, Action<SimpleContainer> overrideContainer)
        {
            container.RegisterWithOverride(typeof(T), key, creator, overrideContainer);
        }

        public static void RegisterWithOverride<T>(this SimpleContainer container, Func<SimpleContainer, Type, string, object> creator, Action<SimpleContainer> overrideContainer)
        {
            container.RegisterWithOverride(typeof(T), null, creator, overrideContainer);
        }


        public static void RegisterWithOverride<T, TImplementation>(this SimpleContainer container, Action<SimpleContainer> overrideContainer)
        {
            container.RegisterWithOverride(typeof(T), null, GetCreator<TImplementation>(), overrideContainer);
        }

        #endregion

    }
}
